using System;
using System.IO;
using System.Net.Sockets;
using System.Threading;
using SimpleSocket.Args;
using SimpleSocket.Packets;

namespace SimpleSocket
{
    internal class ConnectionInfo
    {
        private readonly int _messageBufferSize;
        private readonly Host _host;
        private readonly PacketQueue _sendQueue;
        private readonly Connection _connection;
        private readonly SocketAsyncEventArgsPool _seaPool;

        private SocketAsyncEventArgs _sendSea;
        private SocketAsyncEventArgs _receiveSea;
        private MemoryStream _receiveMemoryStream;
        private Packet _currentSendPacket;

        public ConnectionInfo(Connection connection, Host host)
        {
            _host = host;

            connection.Socket.ReceiveBufferSize = host.SocketBufferSize;
            connection.Socket.SendBufferSize = host.SocketBufferSize;
            connection.Socket.SendTimeout = host.SendDataTimeout;
            connection.Socket.ReceiveTimeout = host.ReceiveDataTimeout;
            connection.Socket.NoDelay = true;
            _connection = connection;

            _seaPool = new SocketAsyncEventArgsPool(host.SocketBufferSize);
            _sendQueue = new PacketQueue(SendInternal);
            _messageBufferSize = host.SocketBufferSize;

            _sendSea = _seaPool.Acquire();
            _sendSea.Completed += SendAsyncCompleted;

            _receiveSea = _seaPool.Acquire();
            _receiveSea.Completed += ReceiveAsyncCompleted;
        }

        #region Release

        private void FreeReceive()
        {
            _receiveSea.Completed -= ReceiveAsyncCompleted;
            _seaPool.Release(_receiveSea);
            _receiveSea = null;
            if (_receiveMemoryStream != null)
            {
                _receiveMemoryStream.Close();
                _receiveMemoryStream.Dispose();
                _receiveMemoryStream = null;
            }
        }

        private void FreeSend()
        {
            _currentSendPacket = null;
            _sendSea.Completed -= SendAsyncCompleted;
            _seaPool.Release(_sendSea);
            _sendSea = null;
        }

        private void FreeSendQueue()
        {
            var result = this._sendQueue.Close();
            if (result.BeforeState == PacketQueue.Closed)
                return;

            if (result.Packets != null)
                foreach (var p in result.Packets)
                    this.OnSendCallback(p, false);

            if (result.BeforeState == PacketQueue.Idle)
                this.FreeSend();
        }

        #endregion

        #region Send

        internal void BeginSend(byte[] data)
        {
            var packet = new Packet(data);
            if (!_sendQueue.TrySend(packet))
                OnSendCallback(packet, false);
        }

        private void SendInternal(Packet packet)
        {
            _currentSendPacket = packet;
            SendInternal(_sendSea);
        }

        private void SendInternal(SocketAsyncEventArgs e)
        {
            var packet = _currentSendPacket;
            // 分包发送
            var sendLen = Math.Min(packet.Payload.Length - packet.SendSize, _messageBufferSize);
            var completed = true;
            try
            {
                Buffer.BlockCopy(packet.Payload, packet.SendSize, e.Buffer, 0, sendLen);
                e.SetBuffer(0, sendLen);
                completed = _connection.SendAsync(e);
            }
            catch (Exception exception)
            {
                BeginDisconnect(exception);
                FreeSend();
                OnSendCallback(packet, false);
            }

            if (!completed)
                ThreadPool.QueueUserWorkItem(_ => SendAsyncCompleted(this, _sendSea));
        }

        private void SendAsyncCompleted(object send, SocketAsyncEventArgs e)
        {
            var packet = _currentSendPacket;

            if (e.SocketError != SocketError.Success)
            {
                BeginDisconnect(new SocketException((int)e.SocketError));
                FreeSend();
                OnSendCallback(packet, false);
                return;
            }

            packet.SendSize += e.BytesTransferred;
            // 包没发送完，继续发送
            if (e.Offset + e.BytesTransferred < e.Count)
            {
                var completed = true;
                try
                {
                    var sendLen = e.Offset + e.BytesTransferred;
                    e.SetBuffer(sendLen, e.Count - sendLen);
                    completed = _connection.SendAsync(e);
                }
                catch (Exception exception)
                {
                    BeginDisconnect(exception);
                    FreeSend();
                    OnSendCallback(packet, false);
                }

                if (!completed)
                    ThreadPool.QueueUserWorkItem(_ => SendAsyncCompleted(this, _sendSea));
            }
            else
            {
                if (packet.IsSent())
                {
                    _currentSendPacket = null;
                    OnSendCallback(packet, true);
                    if (!_sendQueue.TrySendNext())
                        FreeSend();
                    return;
                }

                SendInternal(e);
            }
        }

        private void OnSendCallback(Packet packet, bool success)
        {
            if (!success)
                packet.SendSize = 0;

            _host.OnSendCallback(_connection.RemoteIp, packet, success);
        }

        #endregion

        #region Receive

        internal void BeginReceive()
        {
            Receive();
        }

        private void Receive()
        {
            var completed = true;
            try
            {
                completed = _connection.ReceiveAsync(_receiveSea);
            }
            catch (Exception e)
            {
                BeginDisconnect(e);
            }

            if (!completed)
                ThreadPool.QueueUserWorkItem(_ => ReceiveAsyncCompleted(this, _receiveSea));
        }

        private void ReceiveAsyncCompleted(object send, SocketAsyncEventArgs e)
        {
            if (e.SocketError != SocketError.Success)
            {
                BeginDisconnect(new SocketException((int)e.SocketError));
                FreeReceive();
                return;
            }

            // 传输字节数
            if (e.BytesTransferred < 1)
            {
                BeginDisconnect();
                FreeReceive();
                return;
            }

            ArraySegment<byte> buffer;
            var ts = _receiveMemoryStream;
            if (ts == null || ts.Length == 0)
                buffer = new ArraySegment<byte>(e.Buffer, 0, e.BytesTransferred);
            else
            {
                _receiveMemoryStream.Write(e.Buffer, 0, e.BytesTransferred);
                buffer = new ArraySegment<byte>(ts.GetBuffer(), 0, (int)ts.Length);
            }

            _host.OnReceived(_connection.RemoteIp, new MessageReceivedEventArgs(buffer, MessageReceiveCallback));
        }

        private void MessageReceiveCallback(ArraySegment<byte> payload, int readLength)
        {
            if (readLength < 0 || readLength > payload.Count)
                throw new ArgumentOutOfRangeException(nameof(readLength),
                    "readLength less than 0 or greater than payload.Count.");

            if (payload.Array == null)
                throw new ArgumentNullException(nameof(payload.Array));

            if (readLength == 0)
            {
                if (_receiveMemoryStream == null)
                    _receiveMemoryStream = new MemoryStream(_messageBufferSize);
                else
                    _receiveMemoryStream.SetLength(0);
                _receiveMemoryStream.Write(payload.Array, payload.Offset, payload.Count);
                Receive();
                return;
            }

            if (readLength == payload.Count)
            {
                _receiveMemoryStream?.SetLength(0);
                Receive();
                return;
            }

            //粘包处理
            var args = new MessageReceivedEventArgs(
                new ArraySegment<byte>(payload.Array, payload.Offset + readLength, payload.Count - readLength),
                this.MessageReceiveCallback);

            _host.OnReceived(_connection.RemoteIp, args);
        }

        #endregion

        #region Disconnect

        private void BeginDisconnect(Exception ex = null)
        {
            DisconnectInternal(ex);
        }

        private void DisconnectInternal(Exception ex)
        {
            var args = new SocketAsyncEventArgs();
            args.Completed += this.DisconnectAsyncCompleted;
            args.UserToken = ex;

            var completedAsync = true;
            try
            {
                this._connection.Socket.Shutdown(SocketShutdown.Both);
                completedAsync = this._connection.Socket.DisconnectAsync(args);
            }
            catch (Exception)
            {
                ThreadPool.QueueUserWorkItem(_ => this.DisconnectAsyncCompleted(this, args));
                return;
            }

            if (!completedAsync)
                ThreadPool.QueueUserWorkItem(_ => this.DisconnectAsyncCompleted(this, args));
        }

        private void DisconnectAsyncCompleted(object send, SocketAsyncEventArgs e)
        {
            try
            {
                _connection.Socket.Close();
            }
            catch (Exception exception)
            {
                Console.WriteLine(exception);
            }

            var err = e.UserToken as Exception;
            e.Completed -= DisconnectAsyncCompleted;
            e.Dispose();

            _host.OnDisconnect(_connection.RemoteIp,err);

            FreeSendQueue();
        }

        public void Dispose()
        {
            if (_connection.Active)
            {
                BeginDisconnect();
            }

            _receiveMemoryStream?.Dispose();
            _receiveSea?.Dispose();
            _sendSea?.Dispose();
            _connection.Socket?.Dispose();
        }

        #endregion
    }
}